package ru.niir.goodfil.db.handlers;

import org.hibernate.Hibernate;
import org.hibernate.StatelessSession;
import ru.niir.goodfil.audit.domain.ClientC1NumChangeEvent;
import ru.niir.goodfil.audit.domain.ClientDebtChangeEvent;
import ru.niir.goodfil.audit.domain.ClientDeliveryInfoChangeEvent;
import ru.niir.goodfil.db.Client;
import ru.niir.goodfil.db.ClientDeliveryInfoExt;
import ru.niir.goodfil.db.User;
import ru.niir.goodfil.exceptions.InvalidIdException;
import ru.niir.goodfil.utils.Utils;
import ru.niir.goodfil.utils.validation.ValidationError;
import ru.niir.goodfil.utils.validation.ValidationProblem;
import ru.niir.goodfil.utils.validation.ValidationResult;

import java.util.Iterator;

import static org.apache.commons.lang.StringUtils.isBlank;
import static ru.niir.goodfil.utils.Utils._assertNotNull;

/**
 * Обработчик для клиентов.
 *
 * @see ru.niir.goodfil.db.Client клиенты
 */
public class ClientHandler extends Handler {


    /**
     * Конструктор
     */
    public ClientHandler(Goodfil gf) {
        super(Client.class, gf);
    }

    /**
     * Проверки для клиентов 1. Наименование компании заполнено 2. Наименование компании уникально (ЭТО НЕ МОЖЕТ
     * ПРОВЕРЯТЬСЯ ИНДЕКСОМ, т.к. поле слишком длинное) 4. EMail менеджера заполнен 5. Менеджер связанный с клиентом:
     * либо null, либо указан верно (FK) 6. maxPrice >= 0 (UNSIGNED) 7. minSum >= 0   (UNSIGNED) 8. debtBank >= 0
     * (UNSIGNED) 9. debtCash >= 0 (UNSIGNED)
     * <p/>
     * На данный момент убраны проверки: 1. ФИО менеджера заполнено 2. Рабочий телефон менеджера заполнен 3. Мобильный
     * телефон менеджера заполнен 4. deliveryTypeId верный (FK)
     */
    public ValidationResult validate(Object o) {
        Client client = (Client) o;
        ValidationResult result = ValidationResult.Ok(Client.class);

        if (isBlank(client.getCompanyName())) {
            result.addValidationError(new ValidationError("companyName", "Наименование компании", client.getCompanyName(), ValidationProblem.NOT_EMPTY));
        }
        if (isBlank(client.getContactEmail())) {
            result.addValidationError(new ValidationError("contactEmail", "EMail менеджера", client.getContactEmail(), ValidationProblem.NOT_EMPTY));
        }
        if (!checkClientCompanyNameIsUnique(client.getCompanyName(), client.getId())) {
            result.addValidationError(new ValidationError("companyName", "Наименование компании", client.getCompanyName(), ValidationProblem.UNIQUE));
        }
        if (client.getManagerId() != null && !exists(User.class, client.getManagerId())) {
            result.addValidationError(new ValidationError("managerId", "Менеджер", client.getManagerId(), ValidationProblem.REFERENCE));
        }
        if (client.getMaxPrice() < 0) {
            result.addValidationError(new ValidationError("maxPrice", "maxPrice", client.getMaxPrice(), ValidationProblem.POSITIVE));
        }
        if (client.getMinSum() < 0) {
            result.addValidationError(new ValidationError("minSum", "minSum", client.getMinSum(), ValidationProblem.POSITIVE));
        }
        if (client.getDebtBank() < 0) {
            result.addValidationError(new ValidationError("debtBank", "Долг по банку", client.getDebtBank(), ValidationProblem.POSITIVE));
        }
        if (client.getDebtCash() < 0) {
            result.addValidationError(new ValidationError("debtCash", "Долг по кассе", client.getDebtCash(), ValidationProblem.POSITIVE));
        }

//      Теперь эти поля можно не заполнять
//        if (isBlank(client.getContactFio())) result.addValidationError(new ValidationError("contactFio", "ФИО менедежра", client.getContactFio(), ValidationProblem.NOT_EMPTY));
//        if (isBlank(client.getContactPhoneMob())) result.addValidationError(new ValidationError("contactPhoneMob", "Мобильный телефон менеджера", client.getContactPhoneMob(), ValidationProblem.NOT_EMPTY));
//        if (isBlank(client.getContactPhoneWork())) result.addValidationError(new ValidationError("contactPhoneWork", "Рабочий телефон менедежра", client.getContactPhoneWork(), ValidationProblem.NOT_EMPTY));

//      Это теперь deprecated
//        if (!DeliveryType.isInRange(client.getDeliveryTypeId())) result.addValidationError(new ValidationError("deliveryType", "Тип доставки", client.getDeliveryTypeId(), ValidationProblem.REFERENCE));

        return result;
    }

    /**
     * Возвращает true, если клиента с указаным наименованием не существует. Проверка для функции создания клиента.
     *
     * @param companyName наименование компании (наименование клиента)
     * @return true, если клиента с указаным наименованием не существует.
     */
    private boolean checkClientCompanyNameIsUnique(String companyName) {
        assert companyName != null;

        companyName = companyName.trim();
        return checkClientCompanyNameIsUnique(companyName, -1);
    }

    /**
     * Возвращает true, если клиента с указаным наименованием не существует, КРОМЕ клиента с данным идентификатором.
     * Проверка для функции редактирования клиента.
     *
     * @param companyName наименование компании (наименование клиента)
     * @param id          идентификатор клиента
     * @return true, если клиента с указаным наименованием не существует, КРОМЕ клиента с данным идентификатором
     */
    private boolean checkClientCompanyNameIsUnique(String companyName, long id) {
        assert companyName != null;

        companyName = companyName.trim();
        return (((Long) getSession().createQuery("select count(*) from Client where companyName = ? and id != ?")
                .setString(0, companyName)
                .setLong(1, id)
                .uniqueResult()) == 0);
    }

    /**
     * Перед удалением клиента удаляются связанные с ним: 1. Заказы 2. Персональные цены 3. Пользователи
     *
     * @param id идентификатор объекта, который собираемся удалить
     */
    public void preRemove(long id) {
        if (!exists(klass, id)) {
            throw new InvalidIdException(klass, id);
        }

        //  Вместо этого теперь триггер: PreDeleteClientTrigger
//        getSession().createQuery("delete from Order where clientId =?").setLong(0, id).executeUpdate();
//        getSession().createQuery("delete from PersonalPrice where clientId =?").setLong(0, id).executeUpdate();
//        getSession().createQuery("delete from User where clientId =?").setLong(0, id).executeUpdate();
    }

    /**
     * Функция подгружает к клиенту информацию о его принадлежности к клиентским группам из таблицы
     * `ClientsClientgroups`
     */
    public static void updateClientgroupsInfo(StatelessSession session, Client client) {
        _assertNotNull(session);
        _assertNotNull(client);

        if (client.getClientgroups().size() == 0) {
            return;
        }

        Iterator i = client.getClientgroups().keySet().iterator();
        while (i.hasNext()) {
            Object brandId = i.next();
            Object clientGroupId = client.getClientgroups().get(brandId);

            long lBrandId = new Long(brandId.toString());
            long lClientGroupId = new Long(clientGroupId.toString());
            Long LClientGroupId = (lClientGroupId == 0 || lClientGroupId == -1) ? null : lClientGroupId;

            session.createSQLQuery("INSERT INTO `ClientsClientgroups`(`IdClient`,`IdBrand`,`IdClientGroup`) " +
                    "VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE `IdClientGroup` = VALUES(`IdClientGroup`)")
                    .setLong(0, client.getId())
                    .setLong(1, lBrandId)
                    .setParameter(2, LClientGroupId, Hibernate.LONG)
                    .executeUpdate();
        }
    }

    /**
     * Функция подгружает к клиенту информацию о видимых для него колонках цен из таблицы
     * `con_clients_clientgroups_visible`
     */
    public static void updateVisibleClientgroupsInfo(StatelessSession session, Client client) {
        _assertNotNull(session);
        _assertNotNull(client);

        if (client.getVisibleClientGroups() == null) {
            return;
        }

        session.createSQLQuery("DELETE FROM `ConClientsClientgroupsVisible` WHERE `IdClient` = ?").setLong(0, client.getId()).executeUpdate();
        Iterator<Long> i = client.getVisibleClientGroups().iterator();
        while (i.hasNext()) {
            long clientGroupId = i.next();
            session.createSQLQuery("INSERT INTO `ConClientsClientgroupsVisible`(`IdClient`, `IdClientGroup`) VALUES (?, ?)")
                    .setLong(0, client.getId())
                    .setLong(1, clientGroupId)
                    .executeUpdate();
        }
    }

    /**
     * Фнукция подгружает к клиенту информацию о видимых для него брэндах из таблицы `con_clients_brands_visible`
     */
    public static void updateVisibleBrandsInfo(StatelessSession session, Client client) {
        _assertNotNull(session);
        _assertNotNull(client);

        if (client.getVisibleClientGroups() == null) {
            return;
        }

        session.createSQLQuery("DELETE FROM `ConClientsBrandsVisible` WHERE `IdClient` = ?").setLong(0, client.getId()).executeUpdate();
        Iterator<Long> i = client.getVisibleBrands().iterator();
        while (i.hasNext()) {
            long brandId = i.next();
            session.createSQLQuery("INSERT INTO `ConClientsBrandsVisible`(`IdClient`, `IdBrand`) VALUES (?, ?)")
                    .setLong(0, client.getId())
                    .setLong(1, brandId)
                    .executeUpdate();
        }
    }

    public static void insertOrUpdateClientDeliveryInfoExt(StatelessSession session, Client client) {
        _assertNotNull(session);
        _assertNotNull(client);

        ClientDeliveryInfoExt clientDeliveryInfoExt = client.getDeliveryInfoExt();
        if (clientDeliveryInfoExt == null) {
            return;
        }
        if (clientDeliveryInfoExt.getClientId() == -1) {
            clientDeliveryInfoExt.setClientId(client.getId());
        }


        Object o = session.get(ClientDeliveryInfoExt.class, clientDeliveryInfoExt.getClientId());
        if (o == null) {
            session.insert(clientDeliveryInfoExt);
        } else {
            session.update(clientDeliveryInfoExt);
        }
//        Query query = session.createSQLQuery("INSERT INTO `ClientsDeliveryInfoExt`(" +
//                                            "`IdClient`, " +
//                                            "`PayType`, " +
//                                            "`TransportName`, " +
//                                            "`TransportContactFio`," +
//                                            "`TransportAddress`," +
//                                            "`TransportTel`," +
//                                            "`TargetAddress`," +
//                                            "`TargetContactFio`," +
//                                            "`TargetTel`," +
//                                            "`Period`," +
//                                            "`Comment`) VALUES (" +
//                                            "?, " +
//                                            "?, " +
//                                            "?, " +
//                                            "?, " +
//                                            "?, " +
//                                            "?, " +
//                                            "?," +
//                                            "?, " +
//                                            "?, " +
//                                            "?, " +
//                                            "?) ON DUPLICATE KEY UPDATE " +
//                                            "`PayType` = VALUES(`PayType`), " +
//                                            "`TransportName` = VALUES(`TransportName`), " +
//                                            "`TransportContactFio` = VALUES(`TransportContactFio`)," +
//                                            "`TransportAddress` = VALUES(`TransportAddress`)," +
//                                            "`TransportTel` = VALUES(`TransportTel`)," +
//                                            "`TargetAddress` = VALUES(`TargetAddress`)," +
//                                            "`TargetContactFio` = VALUES(`TargetContactFio`)," +
//                                            "`TargetTel` = VALUES(`TargetTel`)," +
//                                            "`Period` = VALUES(`Period`)," +
//                                            "`Comment` = VALUES(`Comment`);")
//                .setLong(0,       client.getId())
//                .setBoolean(1,    client.getDeliveryInfoExt().getPayType())
//                .setParameter(2,  client.getDeliveryInfoExt().getTransportName(), Hibernate.STRING)
//                .setParameter(3,  client.getDeliveryInfoExt().getTargetContactFio(), Hibernate.STRING)
//                .setParameter(4,  client.getDeliveryInfoExt().getTargetAddress(), Hibernate.STRING)
//                .setParameter(5,  client.getDeliveryInfoExt().getTransportTel(), Hibernate.STRING)
//                .setParameter(6,  client.getDeliveryInfoExt().getTargetAddress(), Hibernate.STRING)
//                .setParameter(7,  client.getDeliveryInfoExt().getTargetContactFio(), Hibernate.STRING)
//                .setParameter(8,  client.getDeliveryInfoExt().getTargetTel(), Hibernate.STRING)
//                .setInteger(9,    client.getDeliveryInfoExt().getPeriod())
//                .setParameter(10, client.getDeliveryInfoExt().getComment(), Hibernate.STRING);
//
//        query.executeUpdate();
    }

    /**
     * Добавить задание на отправку клиента в БД
     *
     * @param o объект - сущность БД
     */
    @Override
    public void postSave(Object o) {
        Client client = (Client) o;

        updateClientgroupsInfo(getSession(), client);
        updateVisibleClientgroupsInfo(getSession(), client);
        updateVisibleBrandsInfo(getSession(), client);
        insertOrUpdateClientDeliveryInfoExt(getSession(), client);

        //  Теперь этим занимается PostInsertClientTrigger
//        DataExchange de = new DataExchange();
//        de.setItemId(client.getId());
//        de.setTableName("Clients");
//        getSession().insert(de);
    }

    @Override
    public void postUpdate(Object o) {
        Client client = (Client) o;

        updateClientgroupsInfo(getSession(), client);
        updateVisibleClientgroupsInfo(getSession(), client);
        updateVisibleBrandsInfo(getSession(), client);
        insertOrUpdateClientDeliveryInfoExt(getSession(), client);

        //  Теперь этим занимается триггер PostUpdateClientTrigger
//        DataExchange de = new DataExchange();
//        de.setItemId(client.getId());
//        de.setTableName("Clients");
//        getSession().insert(de);
    }

    public void __test_insertOrUpdateClientDeliveryInfoExt() {
        Client client = new Client();
        client.setId(1372);
        ClientDeliveryInfoExt clientDeliveryInfoExt = new ClientDeliveryInfoExt();
        clientDeliveryInfoExt.setComment("12345");
        client.setDeliveryInfoExt(clientDeliveryInfoExt);

        insertOrUpdateClientDeliveryInfoExt(getSession(), client);
    }

    @Override
    public Object fullLoadObject(Object o1) {
        Client client = (Client) o1;

        gf.client_appendClientgroups(client);              //  Дополняем объект информацией о клиентских группах
        gf.client_appendVisibleClientgroups(client);       //  Дополняем объект информацией о видимых колонках цен
        gf.client_appendVisibleBrands(client);             //  Дополняем объект информацией о видимых брэндах
        gf.client_appendDeliveryInfo(client);

        return client;
    }

    private void logClientC1NumChangeOnSave(Client client) {
        if (!client.getC1CodeIsMissing()) {
            ClientC1NumChangeEvent event = getAuditHelper().createEvent(ClientC1NumChangeEvent.class);
            event.setClientId(String.valueOf(client.getId()));
            event.setClientName(client.getCompanyName());
            event.setNewC1Num(client.getC1ClientNum());

            getAuditService().logClientC1NumChange(event);
        }
    }

    private void logClientPriceColumnChangeOnSave(Client client) {
       /*  В апдейте сделано так же как и тут, некорректная запись в аудит мешает работе фунцкции постановки галки о рассылке
        for (Long brandId : client.getClientgroups().keySet()) {
            Long clientGroupId = new Long(client.getClientgroups().get(brandId).toString());

            Brand brand = gf.getBrandById(brandId);
            ClientGroup clientGroup = gf.getClientGroupById(clientGroupId);

            ClientPriceColumnChangeEvent event = getAuditHelper().createEvent(ClientPriceColumnChangeEvent.class);
            event.setClientId(String.valueOf(client.getId()));
            event.setClientName(client.getCompanyName());
            event.setBrandId(String.valueOf(brandId));
            event.setBrandName(brand.getName());
            event.setNewColumnName(clientGroup.getName());

            getAuditService().logClientPriceColumnChange(event);
        }
        */
    }

    private void logClientDeliveryInfoChangeOnSave(Client client) {
        ClientDeliveryInfoChangeEvent event = getAuditHelper().createEvent(ClientDeliveryInfoChangeEvent.class);
        String deliveryInfoString = client.getDeliveryInfoExt().toString();
        event.setClientId(String.valueOf(client.getId()));
        event.setClientName(client.getCompanyName());
        event.setDeliveryInfoNewString(deliveryInfoString);

        getAuditService().logClientDeliveryInfoChange(event);
    }

    private void logClientDebtChangedOnSave(Client client) {
        ClientDebtChangeEvent event = getAuditHelper().createEvent(ClientDebtChangeEvent.class);

        event.setClientId(String.valueOf(client.getId()));
        event.setClientName(client.getCompanyName());
        event.setNewDebtBank(String.valueOf(client.getDebtBank()));
        event.setNewDebtCash(String.valueOf(client.getDebtCash()));

        getAuditService().logClientDebtChanged(event);
    }

    private void logClientDebtChangedOnUpdate(Client client1, Client client2) {
        if (client1.getDebtBank() == client2.getDebtBank() &&
                client1.getDebtCash() == client2.getDebtCash()) {
            return;
        }

        ClientDebtChangeEvent event = getAuditHelper().createEvent(ClientDebtChangeEvent.class);

        event.setClientId(String.valueOf(client1.getId()));
        event.setClientName(client1.getCompanyName());
        event.setOldDebtBank(String.valueOf(client1.getDebtBank()));
        event.setOldDebtCash(String.valueOf(client1.getDebtCash()));
        event.setNewDebtBank(String.valueOf(client2.getDebtBank()));
        event.setNewDebtCash(String.valueOf(client2.getDebtCash()));

        getAuditService().logClientDebtChanged(event);
    }

    private void logClientDeliveryInfoChangeOnUpdate(Client client1, Client client2) {
        if (client1.getDeliveryInfoExt().toString().equals(client2.getDeliveryInfoExt().toString())) {
            return;
        }

        ClientDeliveryInfoChangeEvent event = getAuditHelper().createEvent(ClientDeliveryInfoChangeEvent.class);
        String deliveryInfoString1 = client1.getDeliveryInfoExt().toString();
        String deliveryInfoString2 = client2.getDeliveryInfoExt().toString();
        event.setClientId(String.valueOf(client1.getId()));
        event.setClientName(client1.getCompanyName());
        event.setDeliveryInfoOldString(deliveryInfoString1);
        event.setDeliveryInfoNewString(deliveryInfoString2);

        getAuditService().logClientDeliveryInfoChange(event);
    }

    private void logClientPriceColumnChangeOnUpdate(Client client1, Client client2) {
//        if (client1.getClientgroups().equals(client2.getClientgroups())) {
//            return;
//        }
//
//        List<Brand> brands = gf.getBrands();
//        for (Brand brand : brands) {
//            Long brandId = brand.getId();
//
//            Long clientGroupId1 = new Long(client1.getClientgroups().get(brandId).toString());
//            Long clientGroupId2 = new Long(client2.getClientgroups().get(brandId).toString());
//
//            if (!clientGroupId1.equals(clientGroupId2)) {
//                ClientGroup clientGroup1 = gf.getClientGroupById(clientGroupId1);
//                ClientGroup clientGroup2 = gf.getClientGroupById(clientGroupId2);
//
//                ClientPriceColumnChangeEvent event = getAuditHelper().createEvent(ClientPriceColumnChangeEvent.class);
//                event.setClientId(String.valueOf(client1.getId()));
//                event.setClientName(client1.getCompanyName());
//                event.setBrandId(String.valueOf(brandId));
//                event.setBrandName(brand.getName());
//                event.setOldColumnName(clientGroup1.getName());
//                event.setOldColumnName(clientGroup2.getName());
//
//                getAuditService().logClientPriceColumnChange(event);
//            }
//        }
    }

    private void logClientC1NumChangeOnUpdate(Client client1, Client client2) {
        if (Utils.stringsEquals(client1.getC1ClientNum(), client2.getC1ClientNum())) {
            return;
        }

        ClientC1NumChangeEvent event = getAuditHelper().createEvent(ClientC1NumChangeEvent.class);
        event.setClientId(String.valueOf(client1.getId()));
        event.setClientName(client1.getCompanyName());
        event.setOldC1Num(client1.getC1ClientNum());
        event.setNewC1Num(client2.getC1ClientNum());

        getAuditService().logClientC1NumChange(event);
    }


    @Override
    protected void afterTransactionCommitOnSave(Object o1) {
        Client client = (Client) o1;

        logClientC1NumChangeOnSave(client);
        logClientPriceColumnChangeOnSave(client);
//        logClientDeliveryInfoChangeOnSave(client);
//        logClientDebtChangedOnSave(client);
    }

    @Override
    protected void afterTransactionCommitOnUpdate(Object o1, Object o2) {
        Client client1 = (Client) o1;
        Client client2 = (Client) o2;

//        logClientC1NumChangeOnUpdate(client1, client2);
//        logClientPriceColumnChangeOnUpdate(client1, client2);
//        logClientDeliveryInfoChangeOnUpdate(client1, client2);
//        logClientDebtChangedOnUpdate(client1, client2);
    }


    public static void main(String[] args) {
        Goodfil gf = new Goodfil();
        ClientHandler clientHandler = new ClientHandler(gf);
        clientHandler.__test_insertOrUpdateClientDeliveryInfoExt();
    }
}
